﻿#region License
// Copyright (c) 2007 James Newton-King
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
#endregion

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Net.Mail;
using System.Text;
using Newtonsoft.Utilities.Reflection;
using NUnit.Framework;

namespace Newtonsoft.Utilities.Tests.Reflection
{
  public class TypeExtensionsTests : TestsBase
  {
    public abstract class AbstractDefaultConstructor
    {
      private int _dummy;

      public int Dummy
      {
        get { return _dummy; }
        set { _dummy = value; }
      }

      public AbstractDefaultConstructor()
      {
        _dummy = int.MaxValue;
      }
    }
    
    #region IsInstantiatableType
    [Test]
    public void IsInstantiatableType()
    {
      Assert.IsFalse(typeof(string).IsInstantiatableType());
      Assert.IsTrue(typeof(Version).IsInstantiatableType());
      Assert.IsTrue(typeof(int).IsInstantiatableType());
      Assert.IsTrue(typeof(List<string>).IsInstantiatableType());
      Assert.IsFalse(typeof(void).IsInstantiatableType());
      Assert.IsFalse(typeof(Nullable<>).IsInstantiatableType());
      Assert.IsFalse(typeof(List<>).IsInstantiatableType());
      Assert.IsFalse(typeof(int[]).IsInstantiatableType());
      Assert.IsFalse(typeof(ICloneable).IsInstantiatableType());
      Assert.IsFalse(typeof(AbstractDefaultConstructor).IsInstantiatableType());
    }

    [Test]
    [ExpectedException(typeof(ArgumentNullException), ExpectedMessage = @"Value cannot be null.
Parameter name: t")]
    public void IsInstantiatableTypeNull()
    {
      Type t = null;
      t.IsInstantiatableType();
    }
    #endregion

    #region HasDefaultConstructor
    [Test]
    public void HasDefaultConstructor()
    {
      Assert.IsFalse(typeof(string).HasDefaultConstructor());
      Assert.IsTrue(typeof(Version).HasDefaultConstructor());
      Assert.IsTrue(typeof(int).HasDefaultConstructor());
      Assert.IsFalse(typeof(int[]).HasDefaultConstructor());
      Assert.IsFalse(typeof(ICloneable).HasDefaultConstructor());
      Assert.IsTrue(typeof(AbstractDefaultConstructor).HasDefaultConstructor());
    }

    [Test]
    [ExpectedException(typeof(ArgumentNullException), ExpectedMessage = @"Value cannot be null.
Parameter name: t")]
    public void HasDefaultConstructorNull()
    {
      Type t = null;
      t.HasDefaultConstructor();
    }
    #endregion

    [Test]
    public void IsNullable()
    {
      Assert.IsTrue(typeof(string).IsNullable());
      Assert.IsTrue(typeof(ICloneable).IsNullable());
      Assert.IsTrue(typeof(Stream).IsNullable());
      Assert.IsTrue(typeof(int?).IsNullable());
      Assert.IsFalse(typeof(int).IsNullable());
      Assert.IsFalse(typeof(DateTime).IsNullable());
    }

    [Test]
    [ExpectedException(typeof(ArgumentNullException), ExpectedMessage = @"Value cannot be null.
Parameter name: t")]
    public void IsNullableNull()
    {
      Type t = null;
      t.IsNullable();
    }

    [Test]
    public void IsNullableType()
    {
      Assert.IsFalse(typeof(string).IsNullableType());
      Assert.IsFalse(typeof(ICloneable).IsNullableType());
      Assert.IsFalse(typeof(Stream).IsNullableType());
      Assert.IsTrue(typeof(int?).IsNullableType());
      Assert.IsFalse(typeof(int).IsNullableType());
      Assert.IsFalse(typeof(DateTime).IsNullableType());
    }

    [Test]
    [ExpectedException(typeof(ArgumentNullException), ExpectedMessage = @"Value cannot be null.
Parameter name: t")]
    public void IsNullableNullType()
    {
      Type t = null;
      t.IsNullableType();
    }

    [Test]
    public void GetUninitializedValue()
    {
      Assert.AreEqual(0, typeof(int).GetUninitializedValue());
      Assert.AreEqual(0, typeof(decimal).GetUninitializedValue());
      Assert.AreEqual(null, typeof(object).GetUninitializedValue());
      Assert.AreEqual(null, typeof(ICloneable).GetUninitializedValue());
      Assert.AreEqual(null, typeof(void).GetUninitializedValue());
      Assert.AreEqual(StringComparison.CurrentCulture, typeof(StringComparison).GetUninitializedValue());
      Assert.AreEqual(null, typeof(List<int>).GetUninitializedValue());
      Assert.AreEqual(Guid.Empty, typeof(Guid).GetUninitializedValue());
      Assert.AreEqual(DateTime.MinValue, typeof(DateTime).GetUninitializedValue());
    }

    [Test]
    [ExpectedException(typeof(ArgumentException), ExpectedMessage = @"Type System.Collections.Generic.List`1[T] is a generic type definition and cannot be instantiated.
Parameter name: type")]
    public void CreateUninitializedValueGenericTypeDefinition()
    {
      typeof(List<>).GetUninitializedValue();
    }

    [Test]
    [ExpectedException(typeof(ArgumentNullException), ExpectedMessage = @"Value cannot be null.
Parameter name: type")]
    public void CreateUninitializedValueNullType()
    {
      Type t = null;
      t.GetUninitializedValue();
    }

    [Test]
    public void InheritsGenericDefinition()
    {
      Assert.IsFalse(typeof(EqualityComparer<string>).InheritsGenericDefinition(typeof(Collection<>)));
      Assert.IsTrue(typeof(AttachmentCollection).InheritsGenericDefinition(typeof(Collection<>)));
    }

    [Test]
    public void InheritsGenericDefinitionImplementingType()
    {
      Type implementingType;

      Assert.IsTrue(typeof(Collection<>).InheritsGenericDefinition(typeof(Collection<>), out implementingType));
      Assert.AreEqual(typeof(Collection<>), implementingType);

      Assert.IsTrue(typeof(AttachmentCollection).InheritsGenericDefinition(typeof(Collection<>), out implementingType));
      Assert.AreEqual(typeof(Collection<Attachment>), implementingType);

      Assert.IsTrue(typeof(Collection<>).InheritsGenericDefinition(typeof(Collection<>), out implementingType));
      Assert.AreEqual(typeof(Collection<>), implementingType);

      Assert.IsFalse(typeof(Collection<>).InheritsGenericDefinition(typeof(Dictionary<,>), out implementingType));
      Assert.AreEqual(null, implementingType);
    }

    [Test]
    public void ImplementsGenericDefinition()
    {
      Assert.IsTrue(typeof(EqualityComparer<string>).ImplementsGenericDefinition(typeof(IEqualityComparer<>)));
      Assert.IsFalse(typeof(EqualityComparer<string>).ImplementsGenericDefinition(typeof(IList<>)));
      Assert.IsTrue(typeof(Dictionary<string, string>).ImplementsGenericDefinition(typeof(IDictionary<,>)));
    }

    [Test]
    public void ImplementsGenericDefinitionImplementingType()
    {
      Type implementingType;

      Assert.IsTrue(typeof(AttachmentCollection).ImplementsGenericDefinition(typeof(ICollection<>), out implementingType));
      Assert.AreEqual(typeof(ICollection<Attachment>), implementingType);

      Assert.IsTrue(typeof(IList<int>).ImplementsGenericDefinition(typeof(ICollection<>), out implementingType));
      Assert.AreEqual(typeof(ICollection<int>), implementingType);

      Assert.IsTrue(typeof(EqualityComparer<string>).ImplementsGenericDefinition(typeof(IEqualityComparer<>), out implementingType));
      Assert.AreEqual(typeof(IEqualityComparer<string>), implementingType);

      Assert.IsTrue(typeof(IList<int>).ImplementsGenericDefinition(typeof(ICollection<>), out implementingType));
      Assert.AreEqual(typeof(ICollection<int>), implementingType);
    }

    [Test]
    [ExpectedException(typeof(ArgumentNullException), ExpectedMessage = @"Value cannot be null.
Parameter name: type")]
    public void ImplementsGenericDefinitionNullType()
    {
      Type t = null;
      t.ImplementsGenericDefinition(typeof(ICollection<>));
    }

    [Test]
    [ExpectedException(typeof(ArgumentNullException), ExpectedMessage = @"Value cannot be null.
Parameter name: genericInterfaceDefinition")]
    public void ImplementsGenericDefinitionNullDefinition()
    {
      typeof(object).ImplementsGenericDefinition(null);
    }

    #region GetListItemType
    [Test]
    public void GetTypedListItemType()
    {
      Assert.AreEqual(typeof(int), typeof(IList<int>).GetCollectionItemType());
      Assert.AreEqual(typeof(int), typeof(int[]).GetCollectionItemType());
      Assert.AreEqual(typeof(Attachment), typeof(AttachmentCollection).GetCollectionItemType());
      Assert.AreEqual(typeof(List<Version>), typeof(List<List<Version>>).GetCollectionItemType());
    }

    [Test]
    public void GetTypedListItemTypeGenericDefinition()
    {
      Type genericParameter = typeof(IList<>).GetCollectionItemType();
      Assert.IsTrue(genericParameter.IsGenericParameter);
    }

    [Test]
    [ExpectedException(typeof(ArgumentNullException), ExpectedMessage = @"Value cannot be null.
Parameter name: type")]
    public void GetTypedListItemTypeNull()
    {
      Type t = null;
      t.GetCollectionItemType();
    }

    [Test]
    public void GetTypedListItemTypeArrayList()
    {
      Type arrayListType = typeof(ArrayList).GetCollectionItemType();
      Assert.AreEqual(null, arrayListType);
    }

    [Test]
    [ExpectedException(typeof(Exception), ExpectedMessage = @"Type System.Object is not a collection.")]
    public void GetTypedListItemTypeObject()
    {
      typeof(object).GetCollectionItemType();
    }
    #endregion

    #region GetDictionaryValueType
    public class TestGenericDictionary : Dictionary<string, Version>
    {
    }

    [Test]
    public void GetTypedDictionaryValueType()
    {
      Assert.AreEqual(typeof(int), typeof(IDictionary<int, int>).GetDictionaryValueType());
      Assert.AreEqual(typeof(object), typeof(Dictionary<int, object>).GetDictionaryValueType());
      Assert.AreEqual(typeof(object), typeof(Dictionary<int, object>).GetDictionaryValueType());
      Assert.AreEqual(typeof(Version), typeof(TestGenericDictionary).GetDictionaryValueType());
      Assert.AreEqual(typeof(IDictionary<Type, Type>), typeof(Dictionary<int, IDictionary<Type, Type>>).GetDictionaryValueType());
    }

    [Test]
    [ExpectedException(typeof(Exception), ExpectedMessage = "Type System.Collections.Generic.IDictionary`2[TKey,TValue] is not a dictionary.")]
    public void GetTypedDictionaryValueTypeGenericDefinition()
    {
      typeof(IDictionary<,>).GetDictionaryValueType();
    }

    [Test]
    [ExpectedException(typeof(ArgumentNullException), ExpectedMessage = @"Value cannot be null.
Parameter name: type")]
    public void GetTypedDictionaryValueTypeNull()
    {
      Type t = null;
      t.GetDictionaryValueType();
    }

    [Test]
    public void GetTypedDictionaryValueTypeHashtable()
    {
      Type hashtableValueType = typeof(Hashtable).GetDictionaryValueType();
      Assert.AreEqual(null, hashtableValueType);
    }

    [Test]
    [ExpectedException(typeof(Exception), ExpectedMessage = @"Type System.Object is not a dictionary.")]
    public void GetTypedDictionaryValueTypeObject()
    {
      typeof(object).GetDictionaryValueType();
    }
    #endregion
  }
}